package exabase.com.dataTransfer;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import exabase.com.Request;
import exabase.com.RequestEnum;
import exabase.data.Hash;
import exabase.meta.MetaStore;

public class DataTransfer {
	private static Logger logger = Logger.getLogger(DataTransfer.class
			.getName());
	private static int fileNumber = 0;

	protected Socket socket;
	protected InputStream inStream;
	protected ObjectInputStream objectInStream;
	protected OutputStream outStream;
	protected ObjectOutputStream objectOutStream;

	@SuppressWarnings("unchecked")
	public List<PeerInfo> sendAndGetPeerInfo(PeerInfo peerInfo)
			throws IOException, ClassNotFoundException {
		Request request = new Request(RequestEnum.SEND_AND_GET_PEER_INFO);
		logger.info("start sendAndGetPeerInfo. sending request: " + request);
		sendRequest(request);
		logger.info("request sent. trying to get ack.");
		Request ack = getRequest(500);
		logger.info("received ack: " + ack);
		if (request.getId() == ack.getId()
				&& ack.getRequest() == RequestEnum.ACK) {
			logger.info("sending peerInfo " + peerInfo);
			sendObject(peerInfo);
			logger.info("sent peerInfo, trying to receive list");
			List<PeerInfo> list = (List<PeerInfo>) getObjectInStream()
					.readObject();
			logger.info("received  list: " + list + ". Done!");
			return list;
		} else {
			logger.warning("Communication failure, expected id "
					+ request.getId() + " and request "
					+ RequestEnum.ACK.toString() + " but got id " + ack.getId()
					+ " and request " + ack.getRequest() + ".");
			return null;
		}
	}

	public DataTransfer(Socket socket) throws IOException {
		this.socket = socket;
		inStream = socket.getInputStream();
		outStream = socket.getOutputStream();

		logger.fine("Connecting from: " + socket);
	}

	protected ObjectInputStream getObjectInStream() throws IOException {
		if (objectInStream == null)
			objectInStream = new ObjectInputStream(inStream);
		return objectInStream;
	}

	protected ObjectOutputStream getObjectOutStream() throws IOException {
		if (objectOutStream == null)
			objectOutStream = new ObjectOutputStream(outStream);
		return objectOutStream;
	}

	public void sendRequest(Request request) throws IOException {
		sendObject(request);
	}

	public void sendObject(Object object) throws IOException {
		try {
			logger.finest("sending " + object.toString());
			getObjectOutStream().writeObject(object);
			getObjectOutStream().flush();
		} catch (IOException e) {
			logger.log(Level.WARNING, "Error sending object: " + object, e);
			throw e;
		}
	}

	//	
	// public Hash sendDataWithAck(String fileName) throws
	// FileNotFoundException,
	// IOException, ClassNotFoundException {
	// sendRequest(RequestEnum.SEND_DATA_WITH_ACK);
	// ByteStream.toStream(outStream, new File(fileName));
	// return receiveHash();
	// }
	//
	// public void sendData(Data data) throws FileNotFoundException,
	// IOException,
	// ClassNotFoundException {
	// sendRequest(RequestEnum.SEND_DATA);
	// ByteStream.toStream(outStream, new File(data.getFileURL()));
	// }
	//
	// public Hash receiveHash() throws IOException, ClassNotFoundException {
	// return (Hash) getObjectInStream().readObject();
	// }
	//
	// @SuppressWarnings("unchecked")
	// public Set<Meta> getAllMeta() throws IOException, ClassNotFoundException
	// {
	// sendRequest(RequestEnum.BARF);
	// return (Set<Meta>) getObjectInStream().readObject();
	// }

	public Request getRequest(int timeOutMs) throws IOException,
			ClassNotFoundException {
		socket.setSoTimeout(timeOutMs);
		return (Request) getObjectInStream().readObject();
	}

	public void sendBarf() throws IOException {
		sendObject(MetaStore.getInstance().getMetaSet());
	}

	public File receiveFile() throws FileNotFoundException, IOException {
		String fileName = "tmp_data_file" + fileNumber + ".tmp";
		fileNumber++;
		File file = File.createTempFile(fileName, ".tmp");
		ByteStream.toFile(inStream, file);
		return file;
	}

	public void sendHash(Hash hash) throws IOException {
		sendObject(hash);
	}

	//
	// public void sendMeta(Meta meta) throws IOException {
	// sendRequest(RequestEnum.SEND_META);
	// sendObject(meta);
	// }
	//
	// public Meta receiveMeta() throws IOException, ClassNotFoundException {
	// return (Meta) getObjectInStream().readObject();
	// }

	public boolean dataAvailable() throws IOException {
		return getObjectInStream().available() > 0;
	}

	//
	// public void disconnect() throws IOException {
	// sendRequest(RequestEnum.BYE);
	// disconnectNow();
	// }
	//
	// public int ping(int i) throws IOException, ClassNotFoundException {
	// sendRequest(RequestEnum.SEND_I_PING);
	// sendPing(i);
	// return recievePing();
	// }
	//
	// public void sendPing(int i) throws IOException {
	// sendObject(new Integer(i));
	// }
	//
	// public int recievePing() throws IOException, ClassNotFoundException {
	// return ((Integer) getObjectInStream().readObject()).intValue();
	// }

	public void disconnectNow() throws IOException {
		logger.finer("disconnecting socket " + socket.toString());
		if (objectOutStream != null) {
			objectOutStream.flush();
			objectOutStream.close();
		}
		outStream.flush();
		outStream.close();
		if (objectInStream != null) {
			objectInStream.close();
		}
		inStream.close();
		socket.close();
	}

	public Socket getSocket() {
		return socket;
	}

	//
	// public Data getData(Hash hash) throws IOException, ClassNotFoundException
	// {
	// sendRequest(RequestEnum.GET_DATA);
	// sendObject(hash);
	// Request request = getRequest(2000);
	// if (request.getRequest() == RequestEnum.SEND_DATA) {
	// File file = receiveFile();
	// return new Data(file.getAbsolutePath(), hash);
	// } else {
	// logger.info(request.toString());
	// return null;
	// }
	// }

	public Object readObject() throws IOException, ClassNotFoundException {
		return getObjectInStream().readObject();
	}
}
